/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.search;

import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query;

import java.util.List;

/**
 * @version $Id: QueryUtils.java 1172258 2011-09-18 11:42:14Z chrism $
 */
public class QueryUtils {

	/** return true if this query has no positive components */
	public static boolean isNegative(Query q) {
		if (!(q instanceof BooleanQuery))
			return false;
		BooleanQuery bq = (BooleanQuery) q;
		List<BooleanClause> clauses = bq.clauses();
		if (clauses.size() == 0)
			return false;
		for (BooleanClause clause : clauses) {
			if (!clause.isProhibited())
				return false;
		}
		return true;
	}

	/**
	 * Returns the original query if it was already a positive query, otherwise
	 * return the negative of the query (i.e., a positive query).
	 * <p>
	 * Example: both id:10 and id:-10 will return id:10
	 * <p>
	 * The caller can tell the sign of the original by a reference comparison
	 * between the original and returned query.
	 * 
	 * @param q
	 *            Query to create the absolute version of
	 * @return Absolute version of the Query
	 */
	public static Query getAbs(Query q) {
		if (q instanceof WrappedQuery) {
			Query subQ = ((WrappedQuery) q).getWrappedQuery();
			Query absSubQ = getAbs(subQ);
			if (absSubQ == subQ)
				return q;
			WrappedQuery newQ = (WrappedQuery) q.clone();
			newQ.setWrappedQuery(absSubQ);
			return newQ;
		}

		if (!(q instanceof BooleanQuery))
			return q;
		BooleanQuery bq = (BooleanQuery) q;

		List<BooleanClause> clauses = bq.clauses();
		if (clauses.size() == 0)
			return q;

		for (BooleanClause clause : clauses) {
			if (!clause.isProhibited())
				return q;
		}

		if (clauses.size() == 1) {
			// if only one clause, dispense with the wrapping BooleanQuery
			Query negClause = clauses.get(0).getQuery();
			// we shouldn't need to worry about adjusting the boosts since the
			// negative
			// clause would have never been selected in a positive query, and
			// hence would
			// not contribute to a score.
			return negClause;
		} else {
			BooleanQuery newBq = new BooleanQuery(bq.isCoordDisabled());
			newBq.setBoost(bq.getBoost());
			// ignore minNrShouldMatch... it doesn't make sense for a negative
			// query

			// the inverse of -a -b is a OR b
			for (BooleanClause clause : clauses) {
				newBq.add(clause.getQuery(), BooleanClause.Occur.SHOULD);
			}
			return newBq;
		}
	}

	/**
	 * Makes negative queries suitable for querying by lucene.
	 */
	public static Query makeQueryable(Query q) {
		if (q instanceof WrappedQuery) {
			return makeQueryable(((WrappedQuery) q).getWrappedQuery());
		}
		return isNegative(q) ? fixNegativeQuery(q) : q;
	}

	/**
	 * Fixes a negative query by adding a MatchAllDocs query clause. The query
	 * passed in *must* be a negative query.
	 */
	public static Query fixNegativeQuery(Query q) {
		BooleanQuery newBq = (BooleanQuery) q.clone();
		newBq.add(new MatchAllDocsQuery(), BooleanClause.Occur.MUST);
		return newBq;
	}

}
